// GLRendererWin32.cpp
// Matthew DiBernardo
// 04.01.09
//
// Defines the GLRendererWin32 class, a renderer API wrapper. For more
// information on the functionality and purpose of this class, refer
// to GLRendererWin32.h or the commentary below.

#include <glee.h>
#include <glext.h>
#include "GLRendererWin32.h"
#include "Systems\Sphere.h"
#include "..\src\WindowManager.h"
#include "Systems\MeshManager.h"
#include "Systems\AnimationManager.h"
#include "Systems\DecalManager.h"
#include "Systems\GBLabel.h"
#include "Systems\FBOManager.h"
#include "Systems\ShaderManager.h"

using namespace MeshComponent;

GLRendererWin32::GLRendererWin32() : _meshManager(0), _animationManager(0), _decalManager(0), _fboManager(0)
{

}

GLRendererWin32::~GLRendererWin32()
{

}

bool GLRendererWin32::Init(int argc, char** argv)
{
	//***********************//
	// OPENGL INITIALIZATION //
	//***********************//
	_window = (HWND&)argc;

	// Get the window's Device Context
	_deviceContext= GetDC(_window);


	// Define the pixel format descriptor (borrowed from NeHe)
	static	PIXELFORMATDESCRIPTOR pfd=	// pfd Tells Windows How We Want Things To Be
	{
		sizeof(PIXELFORMATDESCRIPTOR),	// Size Of This Pixel Format Descriptor
		1,								// Version Number
		PFD_DRAW_TO_WINDOW |			// Format Must Support Window
		PFD_SUPPORT_OPENGL |			// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,				// Must Support Double Buffering
		PFD_TYPE_RGBA,					// Request An RGBA Format
		0,								// Select Our Color Depth
		0, 0, 0, 0, 0, 0,				// Color Bits Ignored
		0,								// No Alpha Buffer
		0,								// Shift Bit Ignored
		0,								// No Accumulation Buffer
		0, 0, 0, 0,						// Accumulation Bits Ignored
		32,								// 32Bit Z-Buffer (Depth Buffer)
		8,								// 8Bit Stencil Buffer (Max)
		0,								// No Auxiliary Buffer
		PFD_MAIN_PLANE,					// Main Drawing Layer
		0,								// Reserved
		0, 0, 0							// Layer Masks Ignored
	};

	// Set the pixel format
	int nPF = ChoosePixelFormat(_deviceContext, &pfd);
	SetPixelFormat(_deviceContext, nPF, &pfd);

	// Create the OpenGL rendering context
	_renderContext = wglCreateContext(_deviceContext);
	wglMakeCurrent(_deviceContext, _renderContext);

	// Configure the OpenGL context for rendering
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Set the default clear color

	glShadeModel(GL_SMOOTH);

	// Setup Projection Transforms
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(35.0, float(WINDOW_WIDTH)/float(WINDOW_HEIGHT), 15.0, 1000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Normalize any normals automatically
	glEnable(GL_NORMALIZE);

	// AA'd lines -- note this causes see-thru's on objects with the shader enabled
	//glEnable(GL_LINE_SMOOTH);
	//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

	// Singletons
	_meshManager = MeshManager::GetInstance();
	_animationManager = AnimationManager::GetInstance();
	_decalManager = DecalManager::GetInstance();
	//_fboManager = FBOManager::GetInstance();

	//_fboManager->Init(WINDOW_WIDTH, WINDOW_HEIGHT);

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	// Init Camera Matrix
	_cameraTransform.MakeIdentity();

	// VSync enabled: FPS is capped to ~60
	// Only in release mode will VSync occur
#ifdef NDEBUG
	wglSwapIntervalEXT(1);
#endif

	return true;
}

bool GLRendererWin32::Shutdown(void)
{
	//_fboManager->Shutdown();

	return true;
}

void GLRendererWin32::Render3D(const std::vector<IRenderable*>* drawList)
{
	if(drawList->empty()) return;

	int meshID = -1;
	unsigned meshBufferID[MESH_BUFFER_TYPE_MAX];

	// Set the buffers to zero
	memset(meshBufferID, 0, sizeof(unsigned) * MESH_BUFFER_TYPE_MAX);

	// Deref the drawList passed in
	const std::vector<IRenderable*>& list = (*drawList);

	float lightPos[4] = {0, -100.0, -50.0, 1.0};

	glPushMatrix();
	{		
		// Apply a camera transform to all objects in world
		glMultMatrixf(_cameraTransform.M());		

		glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

		// Shader Pass Loop
		for(unsigned char _sPass = 0; _sPass < MAX_SHADER_PASSES; _sPass++)
		{
			// Model Loop (Iterates through all models to be drawn)
			unsigned drawListSize = (unsigned)list.size();
			for(unsigned int i = 0; i < drawListSize; i++)
			{
				// Culled, don't draw it
				if(!list[i]->IsVisible() || list[i]->GetColor().IsInvisible()) continue;

				meshID = list[i]->GetMeshID();

				// No Mesh ID? Get out of here
				if(meshID == -1)
					continue;

				BindBuffers(meshID, meshBufferID);

				if(!meshBufferID[MESH_BUFFER_TYPE_VERTEX]
				|| !meshBufferID[MESH_BUFFER_TYPE_INDEX])
					continue;

				// Shader Binding
				unsigned int shaderID = list[i]->GetShaderID();
				Shader* pShader = NULL;
				bool isPassBound = false;
				if(shaderID)	// (MD) If shader exists...
				{
					// (MD) Use specified shader.
					pShader = ShaderManager::Instance().GetShader(shaderID);

					if(pShader)
					{
						pShader->BindShader();

					}
				}
				else
				{
					// (MD) Otherwise, use no shader.
					// TBD: Add CgFX Support
					ShaderManager::Instance().ReleaseAllShaders();
				}

				// This is the push matrix for the model's transforms.
				// It was moved because glBindTexture() needs to be called
				// after the shader's BindPass() function.
				glPushMatrix();
				{
					glColor4fv(list[i]->GetColor().v);
					glMultMatrixf(list[i]->GetLocalMatrix().M());

					if(pShader)
						isPassBound = pShader->BindPass(_sPass); // retrieve the active pass

					// Texture Binding
					// Overidden from IRenderable
					int textureID = list[i]->GetTextureID();

					// Otherwise check if it's in meshManager
					if(textureID == -1)
						textureID = _meshManager->GetTextureID(meshID);

					if(textureID != -1) 
					{
						glEnable(GL_TEXTURE_2D);
						glBindTexture(GL_TEXTURE_2D, textureID);	// (JB) One texture per mesh for now...

						// Texture Coordinate matrix
						glPushAttrib(GL_TRANSFORM_BIT);
						{
							glMatrixMode(GL_TEXTURE);
							glLoadIdentity();	// Get rid of camera transform

							glMultMatrixf(list[i]->GetTextureMatrix().M());
						}
						glPopAttrib();
					}

					unsigned numTriangles = _meshManager->GetTriangleCount(meshID);

					// Most likely a quad so disable face culling
					if(numTriangles == 2)
						glDisable(GL_CULL_FACE);

					// Drawing
					// Only draw the object if this is the first pass or if the currently
					// bound shader has a pass this time.

					if((_sPass != 0 ? isPassBound : true))
					{
						// Draw the triangles for the mesh
						glDrawRangeElements(GL_TRIANGLES, 0, (numTriangles * 3) - 1, numTriangles * 3, GL_UNSIGNED_SHORT, 0);
					}

					if(numTriangles == 2)
						glEnable(GL_CULL_FACE);
				}
				glPopMatrix();


				// Texture Unbinding
				glDisable(GL_TEXTURE_2D);

				// Shader Unbinding
				if(pShader)
				{
					// TBD: REMOVE THIS LINE AND BELOW WHEN CGFX IS IN //
					glCullFace(GL_BACK);
					glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
					// ** REMOVE THIS LINE AND ABOVE ** //

					pShader->UnbindShader();
					ShaderManager::Instance().ReleaseAllShaders();
				}
			}
		}
	}
	glPopMatrix();

	glBindTexture(GL_TEXTURE_2D, -1);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	// Stop using shaders
	glUseProgram(NULL);
}

void GLRendererWin32::Render2D(const std::vector<IRenderable*>* drawList)
{
	if(drawList->empty()) return;

	int meshID = -1;
	unsigned meshBufferID[MESH_BUFFER_TYPE_MAX];

	// Set the buffers to zero
	memset(meshBufferID, 0, sizeof(unsigned) * MESH_BUFFER_TYPE_MAX);

	// Deref the drawList passed in
	const std::vector<IRenderable*>& list = (*drawList);

	unsigned drawListSize = (unsigned)list.size();
	for(unsigned i = 0; i < drawListSize; i++)
	{
		// Culled, don't draw it
		if(!list[i]->IsVisible() || list[i]->GetColor().IsInvisible()) continue;

		meshID = list[i]->GetMeshID();

		if(meshID == -1)
			continue;

		BindBuffers(meshID, meshBufferID);

		// No verts? Don't draw anything
		if(!meshBufferID[MESH_BUFFER_TYPE_VERTEX]
		|| !meshBufferID[MESH_BUFFER_TYPE_INDEX])
			continue;

		// Texture Binding
		// Overidden from IRenderable
		int textureID = list[i]->GetTextureID();

		// Otherwise check if it's in meshManager
		if(textureID == -1)
			textureID = _meshManager->GetTextureID(meshID);

		if(textureID != -1) 
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, textureID);	// (JB) One texture per mesh for now...

			// Texture Coordinate matrix
			glPushAttrib(GL_TRANSFORM_BIT);
			{
				glMatrixMode(GL_TEXTURE);
				glLoadIdentity();	// Get rid of camera transform

				glMultMatrixf(list[i]->GetTextureMatrix().M());
			}
			glPopAttrib();
		}

		if(!meshBufferID[MESH_BUFFER_TYPE_NORMAL])
		{
			glPushClientAttrib(GL_ENABLE_BIT);
			glDisableClientState(GL_NORMAL_ARRAY);
		}

		if(!meshBufferID[MESH_BUFFER_TYPE_TEXCOORD])
		{
			glPushClientAttrib(GL_ENABLE_BIT);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		unsigned numTriangles = _meshManager->GetTriangleCount(meshID);

		glPushMatrix();
		{
			glColor4fv(list[i]->GetColor().v);
			glMultMatrixf(list[i]->GetLocalMatrix().M());

			// Draw the triangles for the mesh
			glDrawRangeElements(GL_TRIANGLES, 0, (numTriangles * 3) - 1, numTriangles * 3, GL_UNSIGNED_SHORT, 0);
		}
		glPopMatrix();

		if(!meshBufferID[MESH_BUFFER_TYPE_NORMAL])
			glPopClientAttrib();

		if(!meshBufferID[MESH_BUFFER_TYPE_TEXCOORD])
			glPopClientAttrib();

		glDisable(GL_TEXTURE_2D);
	}

	glBindTexture(GL_TEXTURE_2D, -1);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

bool GLRendererWin32::Begin(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);	// Reset to white

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_INDEX_ARRAY);

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_CULL_FACE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// Begin

	return true;
}

bool GLRendererWin32::End(void)
{
	// End

	glUseProgram(NULL);
	glBindTexture(GL_TEXTURE_2D, -1);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glDisable(GL_BLEND);

	glFlush();	// Flushes all GL Calls telling them to stop doing stuff
	SwapBuffers(_deviceContext);

	return true;
}

void GLRendererWin32::Begin2D()
{
	// Note: This will clear all 3D Objects rendered behind
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	// Reset to white
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	// Enable 2D
	int vPort[4];
	glGetIntegerv(GL_VIEWPORT, vPort);

	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	glOrtho(0, vPort[2], 0, vPort[3], -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glDepthMask(FALSE);	// Don't write into the depth buffer
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glUseProgram(NULL);	// Stop using shaders
}

void GLRendererWin32::End2D()
{
	// Disable 2D
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();	

	glDepthMask(TRUE);	// Write into the depth buffer now that we're done with 2D

	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glEnableClientState(GL_NORMAL_ARRAY);
}

void GLRendererWin32::Resize(int width, int height)
{
	// 3D
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(35.0, float(width)/float(height), 15.0, 1000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Resize the FBO Full-Screen quad and texture
	//_fboManager->Resize(width, height);
}

void BindBuffers(const int& meshID, unsigned* meshBufferID, bool fill)
{
	if(!meshBufferID) return;

	// Fill in buffers if they aren't already filled in

	if(fill)
	{
		MeshManager* pMM = MeshManager::GetInstance();
		meshBufferID[MESH_BUFFER_TYPE_VERTEX] = pMM->GetMeshBufferID(meshID, MESH_BUFFER_TYPE_VERTEX);
		meshBufferID[MESH_BUFFER_TYPE_NORMAL] = pMM->GetMeshBufferID(meshID, MESH_BUFFER_TYPE_NORMAL);
		meshBufferID[MESH_BUFFER_TYPE_TEXCOORD] = pMM->GetMeshBufferID(meshID, MESH_BUFFER_TYPE_TEXCOORD);
		meshBufferID[MESH_BUFFER_TYPE_INDEX] = pMM->GetMeshBufferID(meshID, MESH_BUFFER_TYPE_INDEX);
	}

	if(meshBufferID[MESH_BUFFER_TYPE_VERTEX] != 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, meshBufferID[MESH_BUFFER_TYPE_VERTEX]);
		glVertexPointer(3, GL_FLOAT, 0, 0);
	}

	if(meshBufferID[MESH_BUFFER_TYPE_NORMAL] != 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, meshBufferID[MESH_BUFFER_TYPE_NORMAL]);
		glNormalPointer(3, GL_FLOAT, 0);
	}

	if(meshBufferID[MESH_BUFFER_TYPE_TEXCOORD] != 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, meshBufferID[MESH_BUFFER_TYPE_TEXCOORD]);
		glTexCoordPointer(2, GL_FLOAT, 0, 0);
	}

	if(meshBufferID[MESH_BUFFER_TYPE_INDEX] != 0)
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshBufferID[MESH_BUFFER_TYPE_INDEX]);
		glIndexPointer(GL_UNSIGNED_SHORT, 0, 0);
	}
}

void LoadOffsetMatrix(double left, double right, double bottom, double top,
					  double znear, double zfar, float delta, float pz)
{
	float matrix[16];

	glMatrixMode(GL_PROJECTION);
	glFrustum(left, right, bottom, top, znear, zfar);

	glGetFloatv(GL_PROJECTION_MATRIX, matrix);

	// Calculate epsilon
	float epsilon = -2.0f * zfar * znear * delta / ((zfar + znear) * pz * (pz + delta));

	// Modify entry (3, 3) of the projection matrix
	matrix[10] *= 1.0f + epsilon;

	glLoadMatrixf(matrix);
}